#include "JsonParser.h"
#include <cstring>
#include <cmath>
#include <cassert>
#include <cctype>
#include <cstdlib>
#include <cerrno>

namespace wj
{
RetCode JSONParser::Parse(JSONValue& v, const char* jsonStr)
{
    assert(jsonStr != nullptr);
    Context c(jsonStr);
    v.type = JSONType::JSON_NULL;
    parseWhiteSpace(c);
    auto ret = parseValue(c, v);
    if (ret == RetCode::OK)
    {
        parseWhiteSpace(c);
        if (*c.json != '\0')  // there is more ws after value field
        {
            v.type = JSONType::JSON_NULL;
            ret = RetCode::PARSE_ROOT_NOT_SINGULAR;
        }
    }
    return ret;
}

void JSONParser::parseWhiteSpace(Context& c)
{
    const char* p = c.json;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
        p++;
    c.json = p;
}

RetCode JSONParser::parseValue(Context& c, JSONValue& v)
{
    switch (*c.json)
    {
        case 'n':
            return parseLiteral(c, v, "null", JSONType::JSON_NULL);
        case 't':
            return parseLiteral(c, v, "true", JSONType::JSON_TRUE);
        case 'f':
            return parseLiteral(c, v, "false", JSONType::JSON_FALSE);
        default:
            return parseNumber(c, v);
        case '\"':
            return parseString(c, v);
        case '\0':
            return RetCode::PARSE_EXPECT_VALUE;
    }
    return RetCode::OK;
}

RetCode JSONParser::parseLiteral(Context& c,
                                 JSONValue& v,
                                 const char* literal,
                                 JSONType t)
{
    assert(*c.json == literal[0]);
    size_t i = 0;
    for (; literal[i]; ++i)
    {
        if (c.json[i] != literal[i])
        {
            return RetCode::PARSE_INVALID_VALUE;
        }
    }
    c.json += i;
    v.type = t;
    return RetCode::OK;
}

RetCode JSONParser::parseNumber(Context& c, JSONValue& v)
{
    const char* p = c.json;
    if (*p == '-')  // check symbol
    {
        p++;
    }
    if (*p == '0')  // check init 0
    {
        p++;
    }
    else
    {
        if (*p == '0' || !isdigit(*p))
        {
            return RetCode::PARSE_INVALID_VALUE;
        }
        while (isdigit(*p))
            p++;
    }
    if (*p == '.')  // check point
    {
        p++;
        if (!isdigit(*p))
        {
            return RetCode::PARSE_INVALID_VALUE;
        }
        while (isdigit(*p))
            p++;
    }
    if (*p == 'e' || *p == 'E')  // check exponent
    {
        p++;
        if (*p == '+' || *p == '-')
        {
            p++;
        }
        if (!isdigit(*p))
        {
            return RetCode::PARSE_INVALID_VALUE;
        }
        while (isdigit(*p))
            p++;
    }

    v.data = std::strtod(c.json, nullptr);
    if (errno == ERANGE && std::isinf(v.GetNumber()))
    {
        return RetCode::PARSE_NUMBER_OVERFLOW;
    }
    c.json = p;
    v.type = JSONType::JSON_NUMBER;
    return RetCode::OK;
}

RetCode JSONParser::parseString(Context& c, JSONValue& v)
{
    std::string str = "";
    const char* p = c.json;
    assert(*c.json == '\"');
    p = c.json + 1;
    for (;;)
    {
        char ch = *p++;
        switch (ch)
        {
            case '\"':
                v.data = str;
                v.type = JSONType::JSON_STRING;
                c.json = p;
                return RetCode::OK;
            case '\\':  // esape characters
                switch (*p++)
                {
                    case '\"':
                        str += '\"';
                        break;
                    case '\\':
                        str += '\\';
                        break;
                    case '/':
                        str += '/';
                        break;
                    case 'b':
                        str += '\b';
                        break;
                    case 'f':
                        str += '\f';
                        break;
                    case 'n':
                        str += '\n';
                        break;
                    case 'r':
                        str += '\r';
                        break;
                    case 't':
                        str += '\t';
                        break;
                    default:
                        return RetCode::PARSE_INVALID_STRING_ESCAPE;
                }
                break;
            case '\0':
                return RetCode::PARSE_MISS_QUOTEMARK;
            default:
                if (static_cast<uint8_t>(ch) < 0x20)  // not printable chars
                {
                    return RetCode::PARSE_INVALID_STRING_CHAR;
                }
                str.append(1, ch);
        }
    }
}
}  // namespace wj